<!-- build time:Tue Feb 18 2020 17:19:27 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot 定时任务入门 | calm&#39;s blog</title><meta name="description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Job&#x2F;「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述在 JDK 中，内置了两个类，可以实现定时任务的功能：java.util.Timer ：可以通过创建 java.util.TimerTask 调度任务，在同一个线程中串行执行，相互影响。也就是说，对于同一个 Timer 里的多个 T"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 定时任务入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Job&#x2F;「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述在 JDK 中，内置了两个类，可以实现定时任务的功能：java.util.Timer ：可以通过创建 java.util.TimerTask 调度任务，在同一个线程中串行执行，相互影响。也就是说，对于同一个 Timer 里的多个 T"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/1.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/2.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/3.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/4.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/5.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/7.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/6.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/8.png"><meta property="og:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/9.png"><meta property="article:published_time" content="2020-02-18T02:26:31.000Z"><meta property="article:modified_time" content="2020-02-18T04:59:37.626Z"><meta property="article:author" content="calm"><meta property="article:tag" content="job"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/1.png"><link rel="canonical" href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-快速入门-Spring-Task"><span class="toc-text">2. 快速入门 Spring Task</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-引入依赖"><span class="toc-text">2.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-ScheduleConfiguration"><span class="toc-text">2.2 ScheduleConfiguration</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-DemoJob"><span class="toc-text">2.3 DemoJob</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-SpringTaskApplication"><span class="toc-text">2.4 SpringTaskApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-5-Scheduled"><span class="toc-text">2.5 @Scheduled</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-6-应用配置文件"><span class="toc-text">2.6 应用配置文件</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-快速入门-Quartz-单机"><span class="toc-text">3. 快速入门 Quartz 单机</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-引入依赖"><span class="toc-text">3.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-示例-Job"><span class="toc-text">3.2 示例 Job</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-ScheduleConfiguration"><span class="toc-text">3.3 ScheduleConfiguration</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-4-QuartzMemoryApplication"><span class="toc-text">3.4 QuartzMemoryApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-5-应用配置文件"><span class="toc-text">3.5 应用配置文件</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4-再次入门-Quartz-集群"><span class="toc-text">4. 再次入门 Quartz 集群</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#4-1-引入依赖"><span class="toc-text">4.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-2-示例-Job"><span class="toc-text">4.2 示例 Job</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-3-应用配置文件"><span class="toc-text">4.3 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-4-初始化-Quartz-表结构"><span class="toc-text">4.4 初始化 Quartz 表结构</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-5-DataSourceConfiguration"><span class="toc-text">4.5 DataSourceConfiguration</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-6-定时任务配置"><span class="toc-text">4.6 定时任务配置</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#4-6-1-Bean-自动设置"><span class="toc-text">4.6.1 Bean 自动设置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-6-2-Scheduler-手动设置"><span class="toc-text">4.6.2 Scheduler 手动设置</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-7-QuartzJdbcApplication"><span class="toc-text">4.7 QuartzJdbcApplication</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5-快速入门-XXL-JOB"><span class="toc-text">5. 快速入门 XXL-JOB</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#5-1-引入依赖"><span class="toc-text">5.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-2-应用配置文件"><span class="toc-text">5.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-3-XxlJobConfiguration"><span class="toc-text">5.3 XxlJobConfiguration</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-4-DemoJob"><span class="toc-text">5.4 DemoJob</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-5-XxlJobApplication"><span class="toc-text">5.5 XxlJobApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-6-新增执行器"><span class="toc-text">5.6 新增执行器</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-7-新建任务"><span class="toc-text">5.7 新建任务</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#6-快速入门-Elastic-Job"><span class="toc-text">6. 快速入门 Elastic-Job</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#666-彩蛋"><span class="toc-text">666. 彩蛋</span></a></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-job" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot 定时任务入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2020/02/18/labs-springboot2-job/" class="article-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/job/" rel="tag">job</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2020/02/18/labs-springboot2-job/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 11.7k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 49(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><p>摘要: 原创出处 <a href="http://www.iocoder.cn/Spring-Boot/Job/「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！" target="_blank" rel="noopener">http://www.iocoder.cn/Spring-Boot/Job/「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！</a></p><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>在 JDK 中，内置了两个类，可以实现定时任务的功能：</p><ul><li><a href="https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/misc/Timer.java" target="_blank" rel="noopener">java.util.Timer</a> ：可以通过创建 <a href="https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/misc/TimerTask.java" target="_blank" rel="noopener">java.util.TimerTask</a> 调度任务，在同一个线程中<strong>串行</strong>执行，相互影响。也就是说，对于同一个 Timer 里的多个 TimerTask 任务，如果一个 TimerTask 任务在执行中，其它 TimerTask 即使到达执行的时间，也只能排队等待。因为 Timer 是串行的，同时存在 <a href="https://blog.csdn.net/qincidong/article/details/82526458" target="_blank" rel="noopener">坑坑</a> ，所以后来 JDK 又推出了 ScheduledExecutorService ，Timer 也基本不再使用。</li><li><a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/concurrent/ScheduledExecutorService.java" target="_blank" rel="noopener">java.util.concurrent.ScheduledExecutorService</a> ：在 JDK 1.5 新增，基于线程池设计的定时任务类，每个调度任务都会被分配到线程池中<strong>并发</strong>执行，互不影响。这样，ScheduledExecutorService 就解决了 Timer 串行的问题。</li></ul><p>在日常开发中，我们很少直接使用 Timer 或 ScheduledExecutorService 来实现定时任务的需求。主要有几点原因：</p><ul><li>它们仅支持按照指定频率，不<strong>直接</strong>支持指定时间的定时调度，需要我们结合 Calendar 自行计算，才能实现复杂时间的调度。例如说，每天、每周五、2019-11-11 等等。</li><li>它们是进程级别，而我们为了实现定时任务的高可用，需要部署多个进程。此时需要等多考虑，多个进程下，同一个任务在相同时刻，不能重复执行。</li><li>项目可能存在定时任务较多，需要统一的管理，此时不得不进行二次封装。</li></ul><blockquote><p>关于“<strong>任务</strong>”的叫法，也有叫“<strong>作业</strong>”的。在英文上，有 Task 也有 Job 。本质是一样的，本文两种都会用。</p><p>然后，一般来说是调度任务，定时执行。所以胖友会在本文，或者其它文章中，会看到“调度”或“定时”的字眼儿。</p></blockquote><p>在 Spring 体系中，内置了两种定时任务的解决方案：</p><ul><li><p>第一种，<a href="https://github.com/spring-projects/spring-framework" target="_blank" rel="noopener">Spring Framework</a> 的 <a href="https://github.com/spring-projects/spring-framework/tree/master/spring-core/src/main/java/org/springframework/core/task" target="_blank" rel="noopener">Spring Task</a> 模块，提供了<strong>轻量级</strong>的定时任务的实现。</p></li><li><blockquote><p>注：Spring Framework 已经内置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自动化配置，而 2.X 版本提供了支持。</p></blockquote></li></ul><p>在 Java 生态中，还有非常多优秀的开源的调度任务中间件：</p><ul><li><blockquote><p>唯品会基于 Elastic-Job 之上，演化出了 <a href="https://github.com/vipshop/Saturn" target="_blank" rel="noopener">Saturn</a> 项目。</p></blockquote></li><li><p><a href="https://dolphinscheduler.apache.org/zh-cn/" target="_blank" rel="noopener">Apache DolphinScheduler</a></p></li><li><p><a href="https://www.xuxueli.com/xxl-job/" target="_blank" rel="noopener">XXL-JOB</a></p></li></ul><p>目前国内采用 Elastic-Job 和 XXL-JOB 为主。从「芋道源码」了解到的情况，使用 XXL-JOB 的团队会更多一些，主要是上手较为容易，运维功能更为完善。</p><p>本文，我们会按照 Spring Task、Quartz、XXL-JOB 的顺序，进行分别入门。而在文章的结尾，会简单聊聊分布式定时任务的实现原理。</p><h1 id="2-快速入门-Spring-Task"><a href="#2-快速入门-Spring-Task" class="headerlink" title="2. 快速入门 Spring Task"></a>2. 快速入门 Spring Task</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-springtask" target="_blank" rel="noopener">labs-springboot2-springtask</a> 。</p></blockquote><p>考虑到实际场景下，我们很少使用 Spring Task ，所以本小节会写的比较简洁。如果对 Spring Task 比较感兴趣的胖友，可以自己去阅读 <a href="https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/integration.html#scheduling" target="_blank" rel="noopener">《Spring Framework Documentation —— Task Execution and Scheduling》</a> 文档，里面有 Spring Task 相关的详细文档。</p><p>在本小节，我们会使用 Spring Task 功能，实现一个每 2 秒打印一行执行日志的定时任务。</p><h2 id="2-1-引入依赖"><a href="#2-1-引入依赖" class="headerlink" title="2.1 引入依赖"></a>2.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-springtask<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>因为 Spring Task 是 Spring Framework 的模块，所以在我们引入 <code>spring-boot-starter-web</code> 依赖后，无需特别引入它。</p><p>同时，考虑到我们希望让项目启动时，不自动结束 JVM 进程，所以我们引入了 <code>spring-boot-starter-web</code> 依赖。</p><h2 id="2-2-ScheduleConfiguration"><a href="#2-2-ScheduleConfiguration" class="headerlink" title="2.2 ScheduleConfiguration"></a>2.2 ScheduleConfiguration</h2><p>在 <code>cn.javadog.labs.springboot2.springtask.config</code>包路径下，创建 <code>ScheduleConfiguration</code> 类，配置 Spring Task 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ScheduleConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableScheduling</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ScheduleConfiguration</span> </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在类上，添加 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java" target="_blank" rel="noopener">@EnableScheduling</a> 注解，启动 Spring Task 的定时任务调度的功能。</li></ul><h2 id="2-3-DemoJob"><a href="#2-3-DemoJob" class="headerlink" title="2.3 DemoJob"></a>2.3 DemoJob</h2><p>在 <code>cn.javadog.labs.springboot2.springtask.job</code>包路径下，创建 <code>DemoJob</code> 类，示例定时任务类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoJob.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger counts = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Scheduled</span>(fixedRate = <span class="number">2000</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[execute][定时第 (&#123;&#125;) 次执行]"</span>, counts.incrementAndGet());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在类上，添加 <code>@Component</code> 注解，创建 DemoJob Bean 对象。</li><li>创建 <code>#execute()</code> 方法，实现打印日志。同时，在该方法上，添加 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java" target="_blank" rel="noopener">@Scheduled</a> 注解，设置每 2 秒执行该方法。</li></ul><p>虽然说，<code>@Scheduled</code> 注解，可以添加在一个类上的多个方法上。</p><h2 id="2-4-SpringTaskApplication"><a href="#2-4-SpringTaskApplication" class="headerlink" title="2.4 SpringTaskApplication"></a>2.4 SpringTaskApplication</h2><p>创建 <code>SpringTaskApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringTaskApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行 SpringTaskApplication 类，启动示例项目。输出日志精简如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 初始化一个 ThreadPoolTaskScheduler 任务调度器</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">10</span>:<span class="number">34</span>:<span class="number">49</span><span class="variable">.317</span>  INFO <span class="number">12462</span> --- [           main] o<span class="variable">.s</span><span class="variable">.s</span><span class="variable">.c</span><span class="variable">.ThreadPoolTaskScheduler</span>          : Initializing ExecutorService 'taskScheduler'</span><br><span class="line"># 每 <span class="number">2</span> 秒，执行一次 DemoJob 的任务</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">10</span>:<span class="number">34</span>:<span class="number">49</span><span class="variable">.342</span>  INFO <span class="number">12462</span> --- [    calm-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.springtask</span><span class="variable">.job</span><span class="variable">.DemoJob</span>           : [execute][定时第 (<span class="number">1</span>) 次执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">10</span>:<span class="number">34</span>:<span class="number">51</span><span class="variable">.345</span>  INFO <span class="number">12462</span> --- [    calm-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.springtask</span><span class="variable">.job</span><span class="variable">.DemoJob</span>           : [execute][定时第 (<span class="number">2</span>) 次执行]</span><br></pre></td></tr></table></figure><ul><li>通过日志，我们可以看到，初始化一个 ThreadPoolTaskScheduler 任务调度器。之后，每 2 秒，执行一次 DemoJob 的任务。</li></ul><blockquote><p>下面<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「2.5 @Scheduled」</a>和<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「2.6 应用配置文件」</a>两个小节，是补充知识，建议看看。</p></blockquote><h2 id="2-5-Scheduled"><a href="#2-5-Scheduled" class="headerlink" title="2.5 @Scheduled"></a>2.5 @Scheduled</h2><p><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java" target="_blank" rel="noopener">@Scheduled</a> 注解，设置定时任务的执行计划。</p><p><strong>常用</strong>属性如下：</p><ul><li><code>cron</code> 属性：Spring Cron 表达式。例如说，<code>&quot;0 0 12 * * ?&quot;</code> 表示每天中午执行一次，<code>&quot;11 11 11 11 11 ?&quot;</code> 表示 11 月 11 号 11 点 11 分 11 秒执行一次（哈哈哈）。更多示例和讲解，可以看看 <a href="https://blog.csdn.net/bingduanlbd/article/details/51740913" target="_blank" rel="noopener">《Spring Cron 表达式》</a> 文章。注意，以调用<strong>完成时刻</strong>为开始计时时间。</li><li><code>fixedDelay</code> 属性：固定执行间隔，单位：毫秒。注意，以调用<strong>完成时刻</strong>为开始计时时间。</li><li><code>fixedRate</code> 属性：固定执行间隔，单位：毫秒。注意，以调用<strong>开始时刻</strong>为开始计时时间。</li><li>这三个属性，有点雷同，可以看看 <a href="https://www.iteye.com/blog/guwq2014-2424520" target="_blank" rel="noopener">《@Scheduled 定时任务的fixedRate、fixedDelay、cron 的区别》</a> ，一定要分清楚差异。</li></ul><p><strong>不常用</strong>属性如下：</p><ul><li><code>initialDelay</code> 属性：初始化的定时任务执行延迟，单位：毫秒。</li><li><code>zone</code> 属性：解析 Spring Cron 表达式的所属的时区。默认情况下，使用服务器的本地时区。</li><li><code>initialDelayString</code> 属性：<code>initialDelay</code> 的字符串形式。</li><li><code>fixedDelayString</code> 属性：<code>fixedDelay</code> 的字符串形式。</li><li><code>fixedRateString</code> 属性：<code>fixedRate</code> 的字符串形式。</li></ul><h2 id="2-6-应用配置文件"><a href="#2-6-应用配置文件" class="headerlink" title="2.6 应用配置文件"></a>2.6 应用配置文件</h2><p>在 <code>application.yaml</code> 中，添加 Spring Task 定时任务的配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">task:</span></span><br><span class="line">    <span class="comment"># Spring Task 调度任务的配置，对应 TaskSchedulingProperties 配置类</span></span><br><span class="line">    <span class="attr">scheduling:</span></span><br><span class="line">      <span class="attr">thread-name-prefix:</span> <span class="string">calm-task-</span> <span class="comment"># 线程池的线程名的前缀。默认为 scheduling- ，建议根据自己应用来设置</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">size:</span> <span class="number">10</span> <span class="comment"># 线程池大小。默认为 1 ，根据自己应用来设置</span></span><br><span class="line">      <span class="attr">shutdown:</span></span><br><span class="line">        <span class="attr">await-termination:</span> <span class="literal">true</span> <span class="comment"># 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span></span><br><span class="line">        <span class="attr">await-termination-period:</span> <span class="number">60</span> <span class="comment"># 等待任务完成的最大时长，单位为秒。默认为 0 ，根据自己应用来设置</span></span><br></pre></td></tr></table></figure><ul><li>在 <code>spring.task.scheduling</code> 配置项，Spring Task 调度任务的配置，对应 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingProperties.java" target="_blank" rel="noopener">TaskSchedulingProperties</a> 配置类。</li><li>Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.java" target="_blank" rel="noopener">TaskSchedulingAutoConfiguration</a> 自动化配置类，实现 Spring Task 的自动配置，创建 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java" target="_blank" rel="noopener">ThreadPoolTaskScheduler</a> 基于线程池的任务调度器。本质上，ThreadPoolTaskScheduler 是基于 ScheduledExecutorService 的封装，增强在调度时间上的功能。</li></ul><p><strong>注意</strong>，<code>spring.task.scheduling.shutdown</code> 配置项，是为了实现 Spring Task 定时任务的优雅关闭。我们想象一下，如果定时任务在执行的过程中，如果应用开始关闭，把定时任务需要使用到的 Spring Bean 进行销毁，例如说数据库连接池，那么此时定时任务还在执行中，一旦需要访问数据库，可能会导致报错。</p><ul><li>所以，通过配置 <code>await-termination = true</code> ，实现应用关闭时，等待定时任务执行完成。这样，应用在关闭的时，Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后，再开始 Spring Bean 的销毁。</li><li>同时，又考虑到我们不可能无限等待定时任务全部执行结束，因此可以配置 <code>await-termination-period = 60</code> ，等待任务完成的最大时长，单位为秒。具体设置多少的等待时长，可以根据自己应用的需要。</li></ul><h1 id="3-快速入门-Quartz-单机"><a href="#3-快速入门-Quartz-单机" class="headerlink" title="3. 快速入门 Quartz 单机"></a>3. 快速入门 Quartz 单机</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-quartz-memory" target="_blank" rel="noopener">labs-springboot2-quartz-memory</a> 。</p></blockquote><blockquote><p>FROM <a href="https://www.oschina.net/p/quartz" target="_blank" rel="noopener">https://www.oschina.net/p/quartz</a></p><p>Quartz 是一个开源的作业调度框架，它完全由 Java 写成，并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。</p><p>它有很多特征，如：数据库支持，<strong>集群</strong>，插件，EJB 作业预构建，JavaMail 及其它，支持 cron-like 表达式等等。</p></blockquote><p>在 Quartz 体系结构中，有三个组件非常重要：</p><ul><li>Scheduler ：调度器</li><li>Trigger ：触发器</li><li>Job ：任务</li></ul><p>Quartz 分成单机模式和集群模式。</p><ul><li>本小节，我们先来学习下 Quartz 的单机模式，入门比较快。</li><li>下一下<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「5. 再次入门 Quartz 集群」</a> ，我们再来学习下 Quartz 的集群模式。在生产环境下，<strong>一定一定一定要使用 Quartz 的集群模式</strong>，保证定时任务的高可用。</li></ul><p>下面，让我们开始遨游~</p><h2 id="3-1-引入依赖"><a href="#3-1-引入依赖" class="headerlink" title="3.1 引入依赖"></a>3.1 引入依赖</h2><p>在 <code>pom.xml</code>文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-quartz-memory<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Quartz 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-quartz<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。</p><h2 id="3-2-示例-Job"><a href="#3-2-示例-Job" class="headerlink" title="3.2 示例 Job"></a>3.2 示例 Job</h2><p>在 <code>cn.javadog.labs.springboot2.quartz.memory.job</code> 包路径下，我们来创建示例 Job 。</p><p>创建 <code>DemoJob01</code>类，示例定时任务 01 类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob01</span> <span class="keyword">extends</span> <span class="title">QuartzJobBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger counts = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoService demoService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">executeInternal</span><span class="params">(JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[executeInternal][定时第 (&#123;&#125;) 次执行, demoService 为 (&#123;&#125;)]"</span>, counts.incrementAndGet(),</span><br><span class="line">                demoService);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>继承 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java" target="_blank" rel="noopener">QuartzJobBean</a> 抽象类，实现 <code>#executeInternal(JobExecutionContext context)</code> 方法，执行自定义的定时任务的逻辑。</p></li><li><p>QuartzJobBean 实现了 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/Job.java" target="_blank" rel="noopener">org.quartz.Job</a> 接口，提供了 Quartz 每次创建 Job 执行定时逻辑时，将该 Job Bean 的依赖属性注入。例如说，DemoJob01 需要 <code>@Autowired</code> 注入的 <code>demoService</code>属性。核心代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// QuartzJobBean.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 将当前对象，包装成 BeanWrapper 对象</span></span><br><span class="line">        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(<span class="keyword">this</span>);</span><br><span class="line">        <span class="comment">// 设置属性到 bw 中</span></span><br><span class="line">        MutablePropertyValues pvs = <span class="keyword">new</span> MutablePropertyValues();</span><br><span class="line">        pvs.addPropertyValues(context.getScheduler().getContext());</span><br><span class="line">        pvs.addPropertyValues(context.getMergedJobDataMap());</span><br><span class="line">        bw.setPropertyValues(pvs, <span class="keyword">true</span>);</span><br><span class="line">	&#125; <span class="keyword">catch</span> (SchedulerException ex) &#123;</span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> JobExecutionException(ex);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行提供给子类实现的抽象方法</span></span><br><span class="line">    <span class="keyword">this</span>.executeInternal(context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">executeInternal</span><span class="params">(JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException</span>;</span><br></pre></td></tr></table></figure><ul><li>这样一看，是不是清晰很多。不要惧怕中间件的源码，好奇哪个类或者方法，就点进去看看。反正，又不花钱。</li></ul></li><li><p><code>counts</code> 属性，计数器。用于我们后面我们展示，<strong>每次 DemoJob01 都会被 Quartz 创建出一个新的 Job 对象，执行任务</strong>。这个很重要，也要非常小心。</p></li></ul><p>创建 <code>DemoJob02</code> 类，示例定时任务 02 类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoJob02.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob02</span> <span class="keyword">extends</span> <span class="title">QuartzJobBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">executeInternal</span><span class="params">(JobExecutionContext context)</span> <span class="keyword">throws</span> JobExecutionException </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[executeInternal][我开始的执行了]"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>比较简单，为了后面演示案例之用。</li></ul><h2 id="3-3-ScheduleConfiguration"><a href="#3-3-ScheduleConfiguration" class="headerlink" title="3.3 ScheduleConfiguration"></a>3.3 ScheduleConfiguration</h2><p>在 <code>cn.javadog.labs.springboot2.quartz.memory.config</code> 包路径下，创建 <code>ScheduleConfiguration</code>类，配置上述的两个示例 Job 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ScheduleConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ScheduleConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob01Configuration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> JobDetail <span class="title">demoJob01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> JobBuilder.newJob(DemoJob01<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                    .withIdentity("demoJob01") // 名字为 demoJob01</span><br><span class="line">                    .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Trigger <span class="title">demoJob01Trigger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 简单的调度计划的构造器</span></span><br><span class="line">            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()</span><br><span class="line">                    .withIntervalInSeconds(<span class="number">5</span>) <span class="comment">// 频率。</span></span><br><span class="line">                    .repeatForever(); <span class="comment">// 次数。</span></span><br><span class="line">            <span class="comment">// Trigger 构造器</span></span><br><span class="line">            <span class="keyword">return</span> TriggerBuilder.newTrigger()</span><br><span class="line">                    .forJob(demoJob01()) <span class="comment">// 对应 Job 为 demoJob01</span></span><br><span class="line">                    .withIdentity(<span class="string">"demoJob01Trigger"</span>) <span class="comment">// 名字为 demoJob01Trigger</span></span><br><span class="line">                    .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob02Configuration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> JobDetail <span class="title">demoJob02</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> JobBuilder.newJob(DemoJob02<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                    .withIdentity("demoJob02") // 名字为 demoJob02</span><br><span class="line">                    .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Trigger <span class="title">demoJob02Trigger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 基于 Quartz Cron 表达式的调度计划的构造器</span></span><br><span class="line">            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(<span class="string">"0/10 * * * * ? *"</span>);</span><br><span class="line">            <span class="comment">// Trigger 构造器</span></span><br><span class="line">            <span class="keyword">return</span> TriggerBuilder.newTrigger()</span><br><span class="line">                    .forJob(demoJob02()) <span class="comment">// 对应 Job 为 demoJob02</span></span><br><span class="line">                    .withIdentity(<span class="string">"demoJob02Trigger"</span>) <span class="comment">// 名字为 demoJob02Trigger</span></span><br><span class="line">                    .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>内部创建了 DemoJob01Configuration 和 DemoJob02Configuration 两个配置类，分别配置 DemoJob01 和 DemoJob02 两个 Quartz Job 。</li><li>========== <strong>DemoJob01Configuration</strong> ==========</li><li><code>#demoJob01()</code> 方法，创建 DemoJob01 的 JobDetail Bean 对象。</li><li><code>#demoJob01Trigger()</code> 方法，创建 DemoJob01 的 Trigger Bean 对象。其中，我们使用 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/SimpleScheduleBuilder.java" target="_blank" rel="noopener">SimpleScheduleBuilder</a> 简单的调度计划的构造器，创建了每 5 秒执行一次，无限重复的调度计划。</li><li>========== <strong>DemoJob2Configuration</strong> ==========</li><li><code>#demoJob2()</code> 方法，创建 DemoJob02 的 JobDetail Bean 对象。</li><li><code>#demoJob02Trigger()</code> 方法，创建 DemoJob02 的 Trigger Bean 对象。其中，我们使用 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/CronScheduleBuilder.java" target="_blank" rel="noopener">CronScheduleBuilder</a> 基于 Quartz Cron 表达式的调度计划的构造器，创建了每<strong>第</strong> 10 秒执行一次的调度计划。这里，推荐一个 <a href="http://www.bejson.com/othertools/cron/" target="_blank" rel="noopener">Quartz/Cron/Crontab 表达式在线生成工具</a> ，方便帮我们生成 Quartz Cron 表达式，并计算出最近 5 次运行时间。</li></ul><p>因为 JobDetail 和 Trigger 一般是成双成对出现，所以建议配置成一个 Configuration 配置类。</p><h2 id="3-4-QuartzMemoryApplication"><a href="#3-4-QuartzMemoryApplication" class="headerlink" title="3.4 QuartzMemoryApplication"></a>3.4 QuartzMemoryApplication</h2><p>创建 <code>QuartzMemoryApplication.java</code>类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QuartzMemoryApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行 QuartzMemoryApplication 类，启动示例项目。输出日志精简如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"># 创建了 Quartz QuartzScheduler 并启动</span><br><span class="line">2020-02-18 10:51:45.395  INFO 12492 --- [           main] org.quartz.impl.StdSchedulerFactory      : Using default implementation for ThreadExecutor</span><br><span class="line">2020-02-18 10:51:45.411  INFO 12492 --- [           main] org.quartz.core.SchedulerSignalerImpl    : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl</span><br><span class="line">2020-02-18 10:51:45.411  INFO 12492 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.</span><br><span class="line">2020-02-18 10:51:45.411  INFO 12492 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.</span><br><span class="line">2020-02-18 10:51:45.412  INFO 12492 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) &#39;quartzScheduler&#39; with instanceId &#39;NON_CLUSTERED&#39;</span><br><span class="line">  Scheduler class: &#39;org.quartz.core.QuartzScheduler&#39; - running locally.</span><br><span class="line">  NOT STARTED.</span><br><span class="line">  Currently in standby mode.</span><br><span class="line">  Number of jobs executed: 0</span><br><span class="line">  Using thread pool &#39;org.quartz.simpl.SimpleThreadPool&#39; - with 25 threads.</span><br><span class="line">  Using job-store &#39;org.quartz.simpl.RAMJobStore&#39; - which does not support persistence. and is not clustered.</span><br><span class="line">2020-02-18 10:51:45.412  INFO 12492 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler &#39;quartzScheduler&#39; initialized from an externally provided properties instance.</span><br><span class="line">2020-02-18 10:51:45.412  INFO 12492 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2</span><br><span class="line">2020-02-18 10:51:45.412  INFO 12492 --- [           main] org.quartz.core.QuartzScheduler          : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@29006752</span><br><span class="line">2020-02-18 10:51:45.448  INFO 12492 --- [           main] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now</span><br><span class="line">2020-02-18 10:51:45.448  INFO 12492 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.</span><br><span class="line"></span><br><span class="line"># DemoJob01</span><br><span class="line">2020-02-18 10:51:50.084  INFO 12492 --- [eduler_Worker-3] c.j.l.s.quartz.memory.job.DemoJob01      : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.javadog.labs.springboot2.quartz.memory.service.DemoService@55434a7c)]</span><br><span class="line">2020-02-18 10:51:55.083  INFO 12492 --- [eduler_Worker-4] c.j.l.s.quartz.memory.job.DemoJob01      : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.javadog.labs.springboot2.quartz.memory.service.DemoService@55434a7c)]</span><br><span class="line">2020-02-18 10:52:00.083  INFO 12492 --- [eduler_Worker-6] c.j.l.s.quartz.memory.job.DemoJob01      : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.javadog.labs.springboot2.quartz.memory.service.DemoService@55434a7c)]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># DemoJob02</span><br><span class="line">2020-02-18 10:51:50.002  INFO 12492 --- [eduler_Worker-2] c.j.l.s.quartz.memory.job.DemoJob02      : [executeInternal][我开始的执行了]</span><br><span class="line">2020-02-18 10:52:00.002  INFO 12492 --- [eduler_Worker-5] c.j.l.s.quartz.memory.job.DemoJob02      : [executeInternal][我开始的执行了]</span><br><span class="line">2020-02-18 10:52:10.006  INFO 12492 --- [eduler_Worker-8] c.j.l.s.quartz.memory.job.DemoJob02      : [executeInternal][我开始的执行了]</span><br></pre></td></tr></table></figure><ul><li>项目启动时，会创建了 Quartz QuartzScheduler 并启动。</li><li>考虑到阅读日志方便，艿艿这里把 DemoJob01 和 DemoJob02 的日志分开来了。</li><li>对于 DemoJob01 ，每 5 秒左右执行一次。同时我们可以看到，<code>demoService</code> 成功注入，而 <code>counts</code> 每次都是 1 ，说明每次 DemoJob01 都是新创建的。</li><li>对于 DemoJob02 ，每<strong>第</strong> 10 秒执行一次。</li></ul><blockquote><p>下面<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.5 应用配置文件」</a>两个小节，是补充知识，建议看看。</p></blockquote><h2 id="3-5-应用配置文件"><a href="#3-5-应用配置文件" class="headerlink" title="3.5 应用配置文件"></a>3.5 应用配置文件</h2><p>在 <code>application.yml</code> 中，添加 Quartz 的配置，如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  # Quartz 的配置，对应 QuartzProperties 配置类</span><br><span class="line">  quartz:</span><br><span class="line">    job-store-type: memory # Job 存储器类型。默认为 memory 表示内存，可选 jdbc 使用数据库。</span><br><span class="line">    auto-startup: true # Quartz 是否自动启动</span><br><span class="line">    startup-delay: 0 # 延迟 N 秒启动</span><br><span class="line">    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span><br><span class="line">    overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置</span><br><span class="line">    properties: # 添加 Quartz Scheduler 附加属性，更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档</span><br><span class="line">      org:</span><br><span class="line">        quartz:</span><br><span class="line">          threadPool:</span><br><span class="line">            threadCount: 25 # 线程池大小。默认为 10 。</span><br><span class="line">            threadPriority: 5 # 线程优先级</span><br><span class="line">            class: org.quartz.simpl.SimpleThreadPool # 线程池类型</span><br><span class="line">#    jdbc: # 这里暂时不说明，使用 JDBC 的 JobStore 的时候，才需要配置</span><br></pre></td></tr></table></figure><ul><li>在 <code>spring.quartz</code> 配置项，Quartz 的配置，对应 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java" target="_blank" rel="noopener">QuartzProperties</a> 配置类。</li><li>Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java" target="_blank" rel="noopener">QuartzAutoConfiguration</a> 自动化配置类，实现 Quartz 的自动配置，创建 Quartz <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/Scheduler.java" target="_blank" rel="noopener">Scheduler</a>(<strong>调度器</strong>) Bean 。</li></ul><p><strong>注意</strong>，<code>spring.quartz.wait-for-jobs-to-complete-on-shutdown</code> 配置项，是为了实现 Quartz 的优雅关闭，建议开启。关于这块，和我们在 Spring Task 的<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「2.6 应用配置文件」</a> 提到的是一致的。</p><h1 id="4-再次入门-Quartz-集群"><a href="#4-再次入门-Quartz-集群" class="headerlink" title="4. 再次入门 Quartz 集群"></a>4. 再次入门 Quartz 集群</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-quartz-jdbc" target="_blank" rel="noopener">labs-springboot2-quartz-jdbc</a> 。</p></blockquote><p>实际场景下，我们必然需要考虑定时任务的<strong>高可用</strong>，所以基本上，肯定使用 Quartz 的集群方案。因此本小节，我们使用 Quartz 的 <strong>JDBC</strong> 存储器 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/impl/jdbcjobstore/JobStoreTX.java" target="_blank" rel="noopener">JobStoreTX</a> ，并是使用 MySQL 作为数据库。</p><blockquote><p>FROM <a href="https://blog.csdn.net/Evankaka/article/details/45540885" target="_blank" rel="noopener">https://blog.csdn.net/Evankaka/article/details/45540885</a></p></blockquote><table><thead><tr><th align="left">类型</th><th align="left">优点</th><th align="left">缺点</th></tr></thead><tbody><tr><td align="left">RAMJobStore</td><td align="left">不要外部数据库，配置容易，运行速度快</td><td align="left">因为调度程序信息是存储在被分配给 JVM 的内存里面，所以，当应用程序停止运行时，所有调度信息将被丢失。另外因为存储到JVM内存里面，所以可以存储多少个 Job 和 Trigger 将会受到限制</td></tr><tr><td align="left">JDBC 作业存储</td><td align="left">支持集群，因为所有的任务信息都会保存到数据库中，可以控制事物，还有就是如果应用服务器关闭或者重启，任务信息都不会丢失，并且可以恢复因服务器关闭或者重启而导致执行失败的任务</td><td align="left">运行速度的快慢取决与连接数据库的快慢</td></tr></tbody></table><blockquote><p>艿艿：实际上，有方案可以实现兼具这两种方式的优点，我们在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「666. 彩蛋」</a> 中来说。</p></blockquote><p>另外，本小节提供的示例和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3. 快速入门 Quartz 单机」</a> 基本一致下面，让我们开始遨游~</p><h2 id="4-1-引入依赖"><a href="#4-1-引入依赖" class="headerlink" title="4.1 引入依赖"></a>4.1 引入依赖</h2><p>在 <code>pom.xml</code>文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-quartz-jdbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对数据库连接池的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 本示例，我们使用 MySQL --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Quartz 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-quartz<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.1 引入依赖」</a> 基本一致，只是额外引入 <code>spring-boot-starter-test</code> 依赖，等会会写两个单元测试方法。</li></ul><h2 id="4-2-示例-Job"><a href="#4-2-示例-Job" class="headerlink" title="4.2 示例 Job"></a>4.2 示例 Job</h2><p>在 <code>cn.javadog.labs.springboot2.quartz.jdbc.job</code> 包路径下，创建 <code>DemoJob01</code> 和 <code>DemoJob02</code> 类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoJob01.java</span></span><br><span class="line"><span class="meta">@DisallowConcurrentExecution</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob01</span> <span class="keyword">extends</span> <span class="title">QuartzJobBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoService demoService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">executeInternal</span><span class="params">(JobExecutionContext context)</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[executeInternal][我开始的执行了, demoService 为 (&#123;&#125;)]"</span>, demoService);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// DemoJob02.java</span></span><br><span class="line"><span class="meta">@DisallowConcurrentExecution</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob02</span> <span class="keyword">extends</span> <span class="title">QuartzJobBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">executeInternal</span><span class="params">(JobExecutionContext context)</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[executeInternal][我开始的执行了]"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>相比 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.2 示例 Job」</a> 来说，在类上添加了 Quartz 的 <a href="https://github.com/elventear/quartz-scheduler/blob/master/quartz-core/src/main/java/org/quartz/DisallowConcurrentExecution.java" target="_blank" rel="noopener">@DisallowConcurrentExecution</a> 注解，保证相同 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/JobDetail.java" target="_blank" rel="noopener">JobDetail</a> 在多个 JVM 进程中，有且仅有一个节点在执行。</li></ul><p><strong>注意</strong>，不是以 Quartz <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/Job.java" target="_blank" rel="noopener">Job</a> 为维度，保证在多个 JVM 进程中，有且仅有一个节点在执行，<strong>而是以 JobDetail 为维度</strong>。虽然说，绝大多数情况下，我们会保证一个 Job 和 JobDetail 是<strong>一一对应</strong>。所以，搞不清楚这个概念的胖友，最好搞清楚这个概念。实在有点懵逼，保证一个 Job 和 JobDetail 是<strong>一一对应</strong>就对了。</p><p>而 JobDetail 的<strong>唯一标识</strong>是 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/java/org/quartz/JobKey.java" target="_blank" rel="noopener">JobKey</a> ，使用 <code>name</code> + <code>group</code> 两个属性。一般情况下，我们只需要设置 <code>name</code> 即可，而 Quartz 会默认 <code>group = DEFAULT</code> 。</p><p>不过这里还有一点要补充，也是需要<strong>注意的</strong>，在 Quartz 中，<strong>相同 Scheduler 名字</strong>的节点，形成一个 Quartz 集群。在下文中，我们可以通过 <code>spring.quartz.scheduler-name</code> 配置项，设置 Scheduler 的名字。</p><p><strong>【重要】</strong>为什么要说这个呢？因为我们要完善一下上面的说法：通过在 Job 实现类上添加 <code>@DisallowConcurrentExecution</code> 注解，实现在<strong>相同</strong> Quartz Scheduler 集群中，<strong>相同</strong> JobKey 的 JobDetail ，保证在多个 JVM 进程中，有且仅有一个节点在执行。</p><h2 id="4-3-应用配置文件"><a href="#4-3-应用配置文件" class="headerlink" title="4.3 应用配置文件"></a>4.3 应用配置文件</h2><p>在 <code>application.yaml</code> 中，添加 Quartz 的配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">user:</span></span><br><span class="line">      <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/labs-springboot2?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">      <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">quartz:</span></span><br><span class="line">      <span class="attr">url:</span> <span class="string">jdbc:mysql://127.0.0.1:3306/labs-springboot2?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">      <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">root</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Quartz 的配置，对应 QuartzProperties 配置类</span></span><br><span class="line">  <span class="attr">quartz:</span></span><br><span class="line">    <span class="attr">scheduler-name:</span> <span class="string">clusteredScheduler</span> <span class="comment"># Scheduler 名字。默认为 schedulerName</span></span><br><span class="line">    <span class="attr">job-store-type:</span> <span class="string">jdbc</span> <span class="comment"># Job 存储器类型。默认为 memory 表示内存，可选 jdbc 使用数据库。</span></span><br><span class="line">    <span class="attr">auto-startup:</span> <span class="literal">true</span> <span class="comment"># Quartz 是否自动启动</span></span><br><span class="line">    <span class="attr">startup-delay:</span> <span class="number">0</span> <span class="comment"># 延迟 N 秒启动</span></span><br><span class="line">    <span class="attr">wait-for-jobs-to-complete-on-shutdown:</span> <span class="literal">true</span> <span class="comment"># 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span></span><br><span class="line">    <span class="attr">overwrite-existing-jobs:</span> <span class="literal">false</span> <span class="comment"># 是否覆盖已有 Job 的配置</span></span><br><span class="line">    <span class="attr">properties:</span> <span class="comment"># 添加 Quartz Scheduler 附加属性，更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档</span></span><br><span class="line">      <span class="attr">org:</span></span><br><span class="line">        <span class="attr">quartz:</span></span><br><span class="line">          <span class="comment"># JobStore 相关配置</span></span><br><span class="line">          <span class="attr">jobStore:</span></span><br><span class="line">            <span class="comment"># 数据源名称</span></span><br><span class="line">            <span class="attr">dataSource:</span> <span class="string">quartzDataSource</span> <span class="comment"># 使用的数据源</span></span><br><span class="line">            <span class="attr">class:</span> <span class="string">org.quartz.impl.jdbcjobstore.JobStoreTX</span> <span class="comment"># JobStore 实现类</span></span><br><span class="line">            <span class="attr">driverDelegateClass:</span> <span class="string">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</span></span><br><span class="line">            <span class="attr">tablePrefix:</span> <span class="string">QRTZ_</span> <span class="comment"># Quartz 表前缀</span></span><br><span class="line">            <span class="attr">isClustered:</span> <span class="literal">true</span> <span class="comment"># 是集群模式</span></span><br><span class="line">            <span class="attr">clusterCheckinInterval:</span> <span class="number">1000</span></span><br><span class="line">            <span class="attr">useProperties:</span> <span class="literal">false</span></span><br><span class="line">          <span class="comment"># 线程池相关配置</span></span><br><span class="line">          <span class="attr">threadPool:</span></span><br><span class="line">            <span class="attr">threadCount:</span> <span class="number">25</span> <span class="comment"># 线程池大小。默认为 10 。</span></span><br><span class="line">            <span class="attr">threadPriority:</span> <span class="number">5</span> <span class="comment"># 线程优先级</span></span><br><span class="line">            <span class="attr">class:</span> <span class="string">org.quartz.simpl.SimpleThreadPool</span> <span class="comment"># 线程池类型</span></span><br><span class="line">    <span class="attr">jdbc:</span> <span class="comment"># 使用 JDBC 的 JobStore 的时候，JDBC 的配置</span></span><br><span class="line">      <span class="attr">initialize-schema:</span> <span class="string">never</span> <span class="comment"># 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ，我们手动创建表结构。</span></span><br></pre></td></tr></table></figure><ul><li>配置项比较多，我们主要对比 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.5 应用配置文件」</a> 来看看。</li><li>在 <code>spring.datasource</code> 配置项下，用于创建多个数据源的配置。<ul><li><code>user</code> 配置，连接 <code>labs-springboot2</code> 库。目的是，为了模拟我们一般项目，使用到的业务数据库。</li><li><code>quartz</code> 配置，连接 <code>labs-springboot2</code> 库，模拟测试，所以用的同一个库。 如果我们有多个项目需要使用到 Quartz 数据库的话，<strong>可以统一使用一个</strong>，但是要注意配置 <code>spring.quartz.scheduler-name</code> 设置不同的 Scheduler 名字，形成不同的 Quartz 集群。</li></ul></li><li>在 <code>spring.quartz</code> 配置项下，额外增加了一些配置项，我们逐个来看看。<ul><li><code>scheduler-name</code> 配置，Scheduler 名字。这个我们在上文解释了很多次了，如果还不明白，请拍死自己。</li><li><code>job-store-type</code> 配置，设置了使用 <code>&quot;jdbc&quot;</code> 的 Job 存储器。</li><li><code>properties.org.quartz.jobStore</code> 配置，增加了 JobStore 相关配置。重点是，通过 <code>dataSource</code> 配置项，设置了使用名字为 <code>&quot;quartzDataSource&quot;</code> 的 DataSource 为数据源。在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「4.4 DataSourceConfiguration」</a> 中，我们会使用 <code>spring.datasource.quartz</code> 配置，来创建该数据源。</li><li><code>jdbc</code> 配置项，虽然名字叫这个，主要是为了设置使用 SQL 初始化 Quartz 表结构。这里，我们设置 <code>initialize-schema = never</code> ，我们手动创建表结构。</li></ul></li></ul><p>咳咳咳，配置项确实有点多。如果暂时搞不明白的胖友，可以先简单把 <code>spring.datasource</code> 数据源，修改成自己的即可。</p><h2 id="4-4-初始化-Quartz-表结构"><a href="#4-4-初始化-Quartz-表结构" class="headerlink" title="4.4 初始化 Quartz 表结构"></a>4.4 初始化 Quartz 表结构</h2><p>在 <a href="http://www.quartz-scheduler.org/downloads/" target="_blank" rel="noopener">Quartz Download</a> 地址，下载对应版本的发布包。解压后，我们可以在 <code>src/org/quartz/impl/jdbcjobstore/</code> 目录，看到各种数据库的 Quartz 表结构的初始化脚本。这里，因为我们使用 MySQL ，所以使用 <a href="https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql" target="_blank" rel="noopener">tables_mysql_innodb.sql</a> 脚本。</p><p>在数据库中执行该脚本，完成初始化 Quartz 表结构。如下图所示：<img src="1.png" alt="Quartz 表结构"></p><p>关于每个 Quartz 表结构的说明，可以看看 <a href="https://www.jianshu.com/p/b94ebb8780fa" target="_blank" rel="noopener">《Quartz 框架（二）——JobStore 数据库表字段详解》</a> 文章。实际上，也可以不看，哈哈哈哈。</p><p>我们会发现，每个表都有一个 <code>SCHED_NAME</code> 字段，Quartz Scheduler 名字。这样，实现每个 Quartz 集群，数据层面的拆分。</p><h2 id="4-5-DataSourceConfiguration"><a href="#4-5-DataSourceConfiguration" class="headerlink" title="4.5 DataSourceConfiguration"></a>4.5 DataSourceConfiguration</h2><p>在 <code>cn.javadog.labs.springboot2.quartz.jdbc.config</code> 包路径下，创建 <code>DataSourceConfiguration</code> 类，配置数据源。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DataSourceConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DataSourceConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建 user 数据源的配置对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"userDataSourceProperties"</span>)</span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.datasource.user"</span>) <span class="comment">// 读取 spring.datasource.user 配置到 DataSourceProperties 对象</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSourceProperties <span class="title">userDataSourceProperties</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DataSourceProperties();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建 user 数据源</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"userDataSource"</span>)</span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.datasource.user.hikari"</span>) <span class="comment">// 读取 spring.datasource.user 配置到 HikariDataSource 对象</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSource <span class="title">userDataSource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获得 DataSourceProperties 对象</span></span><br><span class="line">        DataSourceProperties properties =  <span class="keyword">this</span>.userDataSourceProperties();</span><br><span class="line">        <span class="comment">// 创建 HikariDataSource 对象</span></span><br><span class="line">        <span class="keyword">return</span> createHikariDataSource(properties);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建 quartz 数据源的配置对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"quartzDataSourceProperties"</span>)</span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.datasource.quartz"</span>) <span class="comment">// 读取 spring.datasource.quartz 配置到 DataSourceProperties 对象</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSourceProperties <span class="title">quartzDataSourceProperties</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DataSourceProperties();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建 quartz 数据源</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"quartzDataSource"</span>)</span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.datasource.quartz.hikari"</span>)</span><br><span class="line">    <span class="meta">@QuartzDataSource</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSource <span class="title">quartzDataSource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获得 DataSourceProperties 对象</span></span><br><span class="line">        DataSourceProperties properties =  <span class="keyword">this</span>.quartzDataSourceProperties();</span><br><span class="line">        <span class="comment">// 创建 HikariDataSource 对象</span></span><br><span class="line">        <span class="keyword">return</span> createHikariDataSource(properties);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> HikariDataSource <span class="title">createHikariDataSource</span><span class="params">(DataSourceProperties properties)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 HikariDataSource 对象</span></span><br><span class="line">        HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource<span class="class">.<span class="keyword">class</span>).<span class="title">build</span>()</span>;</span><br><span class="line">        <span class="comment">// 设置线程池名</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.hasText(properties.getName())) &#123;</span><br><span class="line">            dataSource.setPoolName(properties.getName());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dataSource;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>基于 <code>spring.datasource.user</code> 配置项，创建了名字为 <code>&quot;userDataSource&quot;</code> 的 DataSource Bean 。并且，在其上我们添加了 <code>@Primay</code> 注解，表示其是<strong>主</strong>数据源。</li><li>基于 <code>spring.datasource.quartz</code> 配置项，创建了名字为 <code>&quot;quartzDataSource&quot;</code> 的 DataSource Bean 。并且，在其上我们添加了 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDataSource.java" target="_blank" rel="noopener">@QuartzDataSource</a> 注解，表示其是 <strong>Quartz</strong> 的数据源。注意，一定要配置!!!!!</li></ul><h2 id="4-6-定时任务配置"><a href="#4-6-定时任务配置" class="headerlink" title="4.6 定时任务配置"></a>4.6 定时任务配置</h2><p>完成上述的工作之后，我们需要配置 Quartz 的定时任务。目前，有两种方式：</p><ul><li>方式一，<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「4.6.1 Bean 自动设置」</a> 。</li><li>方式二，<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「4.6.2 Scheduler 手动设置」</a> 。</li></ul><h3 id="4-6-1-Bean-自动设置"><a href="#4-6-1-Bean-自动设置" class="headerlink" title="4.6.1 Bean 自动设置"></a>4.6.1 Bean 自动设置</h3><p>在 <code>cn.javadog.labs.springboot2.quartz.jdbc.config</code>包路径下，创建 <code>ScheduleConfiguration</code> 类，配置上述的两个示例 Job 。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ScheduleConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ScheduleConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob01Configuration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> JobDetail <span class="title">demoJob01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> JobBuilder.newJob(DemoJob01<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                    .withIdentity("demoJob01") // 名字为 demoJob01</span><br><span class="line">                    .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Trigger <span class="title">demoJob01Trigger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 简单的调度计划的构造器</span></span><br><span class="line">            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()</span><br><span class="line">                    .withIntervalInSeconds(<span class="number">5</span>) <span class="comment">// 频率。</span></span><br><span class="line">                    .repeatForever(); <span class="comment">// 次数。</span></span><br><span class="line">            <span class="comment">// Trigger 构造器</span></span><br><span class="line">            <span class="keyword">return</span> TriggerBuilder.newTrigger()</span><br><span class="line">                    .forJob(demoJob01()) <span class="comment">// 对应 Job 为 demoJob01</span></span><br><span class="line">                    .withIdentity(<span class="string">"demoJob01Trigger"</span>) <span class="comment">// 名字为 demoJob01Trigger</span></span><br><span class="line">                    .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoJob02Configuration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> JobDetail <span class="title">demoJob02</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> JobBuilder.newJob(DemoJob02<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                    .withIdentity("demoJob02") // 名字为 demoJob02</span><br><span class="line">                    .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Trigger <span class="title">demoJob02Trigger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 简单的调度计划的构造器</span></span><br><span class="line">            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(<span class="string">"0/10 * * * * ? *"</span>);</span><br><span class="line">            <span class="comment">// Trigger 构造器</span></span><br><span class="line">            <span class="keyword">return</span> TriggerBuilder.newTrigger()</span><br><span class="line">                    .forJob(demoJob02()) <span class="comment">// 对应 Job 为 demoJob02</span></span><br><span class="line">                    .withIdentity(<span class="string">"demoJob02Trigger"</span>) <span class="comment">// 名字为 demoJob02Trigger</span></span><br><span class="line">                    .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                    .build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.3 ScheduleConfiguration」</a> 是一模一样的。</li></ul><p>在 Quartz 调度器启动的时候，会根据该配置，自动调用如下方法：</p><ul><li><code>Scheduler#addJob(JobDetail jobDetail, boolean replace)</code> 方法，将 JobDetail 持久化到数据库。</li><li><code>Scheduler#scheduleJob(Trigger trigger)</code> 方法，将 Trigger 持久化到数据库。</li></ul><h3 id="4-6-2-Scheduler-手动设置"><a href="#4-6-2-Scheduler-手动设置" class="headerlink" title="4.6.2 Scheduler 手动设置"></a>4.6.2 Scheduler 手动设置</h3><p>一般情况下，卡姆<strong>推荐</strong>使用 Scheduler 手动设置。</p><p>创建 <code>QuartzSchedulerTest</code> 类，创建分别添加 DemoJob01 和 DemoJob02 的 Quartz 定时任务配置。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// QuartzSchedulerTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span>(<span class="title">classes</span> </span>= Application<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">QuartzSchedulerTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> Scheduler scheduler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addDemoJob01Config</span><span class="params">()</span> <span class="keyword">throws</span> SchedulerException </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 JobDetail</span></span><br><span class="line">        JobDetail jobDetail = JobBuilder.newJob(DemoJob01<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                .withIdentity("demoJob01") // 名字为 demoJob01</span><br><span class="line">                .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                .build();</span><br><span class="line">        <span class="comment">// 创建 Trigger</span></span><br><span class="line">        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()</span><br><span class="line">                .withIntervalInSeconds(<span class="number">5</span>) <span class="comment">// 频率。</span></span><br><span class="line">                .repeatForever(); <span class="comment">// 次数。</span></span><br><span class="line">        Trigger trigger = TriggerBuilder.newTrigger()</span><br><span class="line">                .forJob(jobDetail) <span class="comment">// 对应 Job 为 demoJob01</span></span><br><span class="line">                .withIdentity(<span class="string">"demoJob01Trigger"</span>) <span class="comment">// 名字为 demoJob01Trigger</span></span><br><span class="line">                .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                .build();</span><br><span class="line">        <span class="comment">// 添加调度任务</span></span><br><span class="line">        scheduler.scheduleJob(jobDetail, trigger);</span><br><span class="line"><span class="comment">//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addDemoJob02Config</span><span class="params">()</span> <span class="keyword">throws</span> SchedulerException </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 JobDetail</span></span><br><span class="line">        JobDetail jobDetail = JobBuilder.newJob(DemoJob02<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                .withIdentity("demoJob02") // 名字为 demoJob02</span><br><span class="line">                .storeDurably() <span class="comment">// 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时，还没 Trigger 指向它，所以需要设置为 true ，表示保留。</span></span><br><span class="line">                .build();</span><br><span class="line">        <span class="comment">// 创建 Trigger</span></span><br><span class="line">        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(<span class="string">"0/10 * * * * ? *"</span>);</span><br><span class="line">        Trigger trigger = TriggerBuilder.newTrigger()</span><br><span class="line">                .forJob(jobDetail) <span class="comment">// 对应 Job 为 demoJob01</span></span><br><span class="line">                .withIdentity(<span class="string">"demoJob02Trigger"</span>) <span class="comment">// 名字为 demoJob01Trigger</span></span><br><span class="line">                .withSchedule(scheduleBuilder) <span class="comment">// 对应 Schedule 为 scheduleBuilder</span></span><br><span class="line">                .build();</span><br><span class="line">        <span class="comment">// 添加调度任务</span></span><br><span class="line">        scheduler.scheduleJob(jobDetail, trigger);</span><br><span class="line"><span class="comment">//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>创建 JobDetail 和 Trigger 的代码，其实和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「4.6.1 Bean 自动设置」</a> 是一致的。</li><li>在每个单元测试方法的最后，调用 <code>Scheduler#scheduleJob(JobDetail jobDetail, Trigger trigger)</code> 方法，将 JobDetail 和 Trigger 持久化到数据库。</li><li>如果想要覆盖数据库中的 Quartz 定时任务的配置，可以调用 <code>Scheduler#scheduleJob(JobDetail jobDetail, Set triggersForJob, boolean replace)</code> 方法，传入 <code>replace = true</code> 进行覆盖配置。</li></ul><h2 id="4-7-QuartzJdbcApplication"><a href="#4-7-QuartzJdbcApplication" class="headerlink" title="4.7 QuartzJdbcApplication"></a>4.7 QuartzJdbcApplication</h2><p>创建 <code>QuartzJdbcApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// QuartzMemoryApplication.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QuartzJdbcApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>运行 QuartzJdbcApplication 类，启动示例项目。具体的执行日志，和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">「3.4 QuartzMemoryApplication」</a> 基本一致，这里就不重复罗列了。</li></ul><p>如果胖友想要测试集群下的运行情况，可以再创建 创建 <code>QuartzMemoryApplication2.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// QuartzMemoryApplication2.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QuartzMemoryApplication2</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 设置 Tomcat 随机端口</span></span><br><span class="line">        System.setProperty(<span class="string">"server.port"</span>, <span class="string">"0"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 启动 Spring Boot 应用</span></span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>运行 QuartzMemoryApplication2 类，再次启动一个示例项目。然后，观察输出的日志，可以看到启动的两个示例项目，都会有 DemoJob01 和 DemoJob02 的执行日志。</li></ul><h1 id="5-快速入门-XXL-JOB"><a href="#5-快速入门-XXL-JOB" class="headerlink" title="5. 快速入门 XXL-JOB"></a>5. 快速入门 XXL-JOB</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-xxl-job" target="_blank" rel="noopener">labs-springboot2-xxl-job</a> 。</p></blockquote><p>虽然说，Quartz 的功能，已经能够满足我们对定时任务的诉求，但是距离生产可用、好用，还是有一定的距离。在艿艿最早开始实习的时候，因为Quartz 只提供了任务调度的功能，不提供管理任务的管理与监控控制台，需要自己去做二次封装。当时，因为社区中找不到合适的实现这块功能的开源项目，所以我们就自己进行了简单的封装，满足我们的管理与监控的需求。</p><blockquote><p>XXL-JOB 是一个轻量级分布式任务调度平台，其核心设计目标是开发迅速、学习简单、轻量级、易扩展。</p></blockquote><p>对于 XXL-JOB 的入门，「芋道源码」已经在 <a href="http://www.iocoder.cn/XXL-JOB/install/?self" target="_blank" rel="noopener">《芋道 XXL-JOB 安装部署》</a> 中编写，胖友先跳转到该文章阅读。重点是，要先搭建一个 XXL-JOB 调度中心。因为，本文我们是来在 Spring Boot 项目中，实现一个 XXL-JOB 执行器。</p><h2 id="5-1-引入依赖"><a href="#5-1-引入依赖" class="headerlink" title="5.1 引入依赖"></a>5.1 引入依赖</h2><p>在 <code>pom.xml</code>文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-xxl-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.2.1.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- XXL-JOB 相关依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.xuxueli<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>xxl-job-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.1.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>具体每个依赖的作用，胖友自己认真看下添加的所有注释噢。比较可惜的是，目前 XXL-JOB 官方并未提供 Spring Boot Starter 包，略微有点尴尬。不过，社区已经有人在提交 Pull Request 了，详细可见 <a href="https://github.com/xuxueli/xxl-job/pull/820" target="_blank" rel="noopener">https://github.com/xuxueli/xxl-job/pull/820</a> 。</p><h2 id="5-2-应用配置文件"><a href="#5-2-应用配置文件" class="headerlink" title="5.2 应用配置文件"></a>5.2 应用配置文件</h2><p>在 <code>application.yml</code> 中，添加 Quartz 的配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">9090</span> <span class="comment">#指定一个端口，避免和 XXL-JOB 调度中心的端口冲突。仅仅测试之用</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># xxl-job</span></span><br><span class="line"><span class="attr">xxl:</span></span><br><span class="line">  <span class="attr">job:</span></span><br><span class="line">    <span class="attr">admin:</span></span><br><span class="line">      <span class="attr">addresses:</span> <span class="string">http://127.0.0.1:8000/xxl-job-admin</span> <span class="comment"># 调度中心部署跟地址 [选填]：如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调"；为空则关闭自动注册；</span></span><br><span class="line">    <span class="attr">executor:</span></span><br><span class="line">      <span class="attr">appname:</span> <span class="string">calm-xxl-executor</span> <span class="comment"># 执行器 AppName [选填]：执行器心跳注册分组依据；为空则关闭自动注册</span></span><br><span class="line">      <span class="attr">ip:</span> <span class="comment"># 执行器IP [选填]：默认为空表示自动获取IP，多网卡时可手动设置指定IP，该IP不会绑定Host仅作为通讯实用；地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"；</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">6666</span> <span class="comment"># ### 执行器端口号 [选填]：小于等于0则自动获取；默认端口为9999，单机部署多个执行器时，注意要配置不同执行器端口；</span></span><br><span class="line">      <span class="attr">logpath:</span> <span class="string">/Users/yuyong/Desktop/xxl-job/calm-xxl-executor</span> <span class="comment"># 执行器运行日志文件存储磁盘路径 [选填] ：需要对该路径拥有读写权限；为空则使用默认路径；</span></span><br><span class="line">      <span class="attr">logretentiondays:</span> <span class="number">30</span> <span class="comment"># 执行器日志文件保存天数 [选填] ： 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能；</span></span><br><span class="line">    <span class="attr">accessToken:</span> <span class="string">abcdefg</span> <span class="comment"># 执行器通讯TOKEN [选填]：非空时启用；</span></span><br></pre></td></tr></table></figure><ul><li>具体每个参数的作用，胖友自己看下详细的注释哈。</li></ul><h2 id="5-3-XxlJobConfiguration"><a href="#5-3-XxlJobConfiguration" class="headerlink" title="5.3 XxlJobConfiguration"></a>5.3 XxlJobConfiguration</h2><p>在 <code>cn.javadog.labs.springboot2.xxl.job.config</code>包路径下，创建 <code>DataSourceConfiguration</code>类，配置 XXL-JOB 执行器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XxlJobConfiguration.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> cn.javadog.labs.springboot2.xxl.job.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Value;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> com.xxl.job.core.executor.impl.XxlJobSpringExecutor;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XxlJobConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.admin.addresses&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String adminAddresses;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.executor.appname&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String appName;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.executor.ip&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String ip;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.executor.port&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> port;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.accessToken&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String accessToken;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.executor.logpath&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String logPath;</span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;xxl.job.executor.logretentiondays&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> logRetentionDays;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span> <span class="comment">//(initMethod = "start", destroyMethod = "destroy")</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> XxlJobSpringExecutor <span class="title">xxlJobExecutor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建 XxlJobSpringExecutor 执行器</span></span><br><span class="line">        XxlJobSpringExecutor xxlJobSpringExecutor = <span class="keyword">new</span> XxlJobSpringExecutor();</span><br><span class="line">        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);</span><br><span class="line">        xxlJobSpringExecutor.setAppName(appName);</span><br><span class="line">        xxlJobSpringExecutor.setIp(ip);</span><br><span class="line">        xxlJobSpringExecutor.setPort(port);</span><br><span class="line">        xxlJobSpringExecutor.setAccessToken(accessToken);</span><br><span class="line">        xxlJobSpringExecutor.setLogPath(logPath);</span><br><span class="line">        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);</span><br><span class="line">        <span class="comment">// 返回</span></span><br><span class="line">        <span class="keyword">return</span> xxlJobSpringExecutor;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在 <code>#xxlJobExecutor()</code> 方法，创建了 Spring 容器下的 XXL-JOB 执行器 Bean 对象。要注意，方法上添加了的 <code>@Bean</code> 注解，配置了启动和销毁方法。</li></ul><h2 id="5-4-DemoJob"><a href="#5-4-DemoJob" class="headerlink" title="5.4 DemoJob"></a>5.4 DemoJob</h2><p>在 <code>cn.javadog.labs.springboot2.xxl.job.job</code>包路径下，创建 <code>DemoJob</code> 类，示例定时任务类。代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; DemoJob.java</span><br><span class="line"></span><br><span class="line">@Component</span><br><span class="line">@JobHandler(&quot;demoJob&quot;)</span><br><span class="line">public class DemoJob extends IJobHandler &#123;</span><br><span class="line"></span><br><span class="line">    private Logger logger &#x3D; LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    private final AtomicInteger counts &#x3D; new AtomicInteger();</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public ReturnT&lt;String&gt; execute(String param) throws Exception &#123;</span><br><span class="line">        &#x2F;&#x2F; 打印日志</span><br><span class="line">        logger.info(&quot;[execute][定时第 (&#123;&#125;) 次执行]&quot;, counts.incrementAndGet());</span><br><span class="line">        &#x2F;&#x2F; 返回执行成功</span><br><span class="line">        return ReturnT.SUCCESS;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>继承 XXL-JOB <a href="https://github.com/xuxueli/xxl-job/blob/master/xxl-job-core/src/main/java/com/xxl/job/core/handler/IJobHandler.java" target="_blank" rel="noopener">IJobHandler</a> 抽象类，通过实现 <code>#execute(String param)</code> 方法，从而实现定时任务的逻辑。</li><li>在方法上，添加 <a href="https://github.com/xuxueli/xxl-job/blob/master/xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/JobHandler.java" target="_blank" rel="noopener">@JobHandler</a> 注解，设置 JobHandler 的名字。后续，我们在调度中心的控制台中，新增任务时，需要使用到这个名字。</li></ul><p><code>#execute(String param)</code> 方法的返回结果，为 <a href="https://github.com/xuxueli/xxl-job/blob/master/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java" target="_blank" rel="noopener">ReturnT</a> 类型。当返回值符合 <code>“ReturnT.code == ReturnT.SUCCESS_CODE”</code> 时表示任务执行成功，否则表示任务执行失败，而且可以通过 <code>“ReturnT.msg”</code> 回调错误信息给调度中心；从而，在任务逻辑中可以方便的控制任务执行结果。</p><p><code>#execute(String param)</code> 方法的方法参数，为调度中心的控制台中，新增任务时，配置的“任务参数”。一般情况下，不会使用到。</p><h2 id="5-5-XxlJobApplication"><a href="#5-5-XxlJobApplication" class="headerlink" title="5.5 XxlJobApplication"></a>5.5 XxlJobApplication</h2><p>创建<code>XxlJobApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解即可。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XxlJobApplication.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XxlJobApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行 XxlJobApplication 类，启动示例项目。输出日志精简如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># XXL-JOB 启动日志</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">35</span>:<span class="number">49</span><span class="variable">.088</span>  INFO <span class="number">20172</span> --- [           main] c<span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.core</span><span class="variable">.executor</span><span class="variable">.XxlJobExecutor</span>   : &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; xxl-job register jobhandler success, name:demoJob, jobHandler:cn<span class="variable">.javadog</span><span class="variable">.labs</span><span class="variable">.springboot2</span><span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.job</span><span class="variable">.DemoJob</span>@<span class="number">46292372</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">35</span>:<span class="number">49</span><span class="variable">.150</span>  INFO <span class="number">20172</span> --- [           main] c<span class="variable">.x</span><span class="variable">.r</span><span class="variable">.r</span><span class="variable">.provider</span><span class="variable">.XxlRpcProviderFactory</span>   : &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; xxl-rpc, provider factory add service success. serviceKey = com<span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.core</span><span class="variable">.biz</span><span class="variable">.ExecutorBiz</span>, serviceBean = <span class="keyword">class</span> com<span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.core</span><span class="variable">.biz</span><span class="variable">.impl</span><span class="variable">.ExecutorBizImpl</span></span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">35</span>:<span class="number">49</span><span class="variable">.333</span>  INFO <span class="number">20172</span> --- [           main] o<span class="variable">.s</span><span class="variable">.s</span><span class="variable">.concurrent</span><span class="variable">.ThreadPoolTaskExecutor</span>  : Initializing ExecutorService 'applicationTaskExecutor'</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">35</span>:<span class="number">49</span><span class="variable">.364</span>  INFO <span class="number">20172</span> --- [       Thread-<span class="number">9</span>] com<span class="variable">.xxl</span><span class="variable">.rpc</span><span class="variable">.remoting</span><span class="variable">.net</span><span class="variable">.Server</span>          : &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; xxl-rpc remoting server start success, <span class="keyword">nettype</span> = com<span class="variable">.xxl</span><span class="variable">.rpc</span><span class="variable">.remoting</span><span class="variable">.net</span><span class="variable">.impl</span><span class="variable">.netty_http</span><span class="variable">.server</span><span class="variable">.NettyHttpServer</span>, port = <span class="number">6666</span></span><br></pre></td></tr></table></figure><p>此时，因为我们并未在 XXL-JOB 调度中心进行相关的配置，所以 DemoJob 并不会执行。下面，让我们在 XXL-JOB 调度中心进行相应的配置。</p><h2 id="5-6-新增执行器"><a href="#5-6-新增执行器" class="headerlink" title="5.6 新增执行器"></a>5.6 新增执行器</h2><p>浏览器打开 <a href="http://127.0.0.1:8080/xxl-job-admin/jobgroup" target="_blank" rel="noopener">http://127.0.0.1:8080/xxl-job-admin/jobgroup</a> 地址，即「执行器管理」菜单。如下图：<img src="2.png" alt="执行器管理"></p><p>点击「新增执行器」按钮，弹出「新增执行器」界面。如下图：<img src="3.png" alt="新增执行器"></p><p>填写完 <code>&quot;calm-xxl-executor&quot;</code> 执行器的信息，点击「保存」按钮，进行保存。耐心等待一会，执行器会自动注册上来。如下图：<img src="4.png" alt="执行器管理"></p><ul><li>执行器列表中显示在线的执行器列表, 可通过 “OnLine 机器” 查看对应执行器的集群机器。</li></ul><p>相同执行器，有且仅需配置一次即可。</p><h2 id="5-7-新建任务"><a href="#5-7-新建任务" class="headerlink" title="5.7 新建任务"></a>5.7 新建任务</h2><p>浏览器打开 <a href="http://127.0.0.1:8080/xxl-job-admin/jobinfo" target="_blank" rel="noopener">http://127.0.0.1:8080/xxl-job-admin/jobinfo</a> 地址，即「任务管理」菜单。如下图：<img src="5.png" alt="任务管理"></p><p>点击最右边的「新增」按钮，弹出「新增」界面。如下图：<img src="7.png" alt="新增"></p><p>填写完 <code>&quot;demoJob&quot;</code> 任务的信息，点击「保存」按钮，进行保存。如下图：<img src="6.png" alt="任务管理"></p><p>点击 <code>&quot;demoJob&quot;</code> 任务的「操作」按钮，选择「启动」，确认后，该 <code>&quot;demoJob&quot;</code> 任务的状态就变成了 <strong>RUNNING</strong> 。如下图：<img src="8.png" alt="任务管理"></p><p>此时，我们打开执行器的 IDE 界面，可以看到 DemoJob 已经在每分钟执行一次了。日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">53</span>:<span class="number">00</span><span class="variable">.198</span>  INFO <span class="number">20217</span> --- [      Thread-<span class="number">19</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.springboot2</span><span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.job</span><span class="variable">.DemoJob</span>    : [execute][定时第 (<span class="number">1</span>) 次执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">54</span>:<span class="number">00</span><span class="variable">.014</span>  INFO <span class="number">20217</span> --- [      Thread-<span class="number">19</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.springboot2</span><span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.job</span><span class="variable">.DemoJob</span>    : [execute][定时第 (<span class="number">2</span>) 次执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">55</span>:<span class="number">00</span><span class="variable">.198</span>  INFO <span class="number">20217</span> --- [      Thread-<span class="number">19</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.springboot2</span><span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.job</span><span class="variable">.DemoJob</span>    : [execute][定时第 (<span class="number">3</span>) 次执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">12</span>:<span class="number">56</span>:<span class="number">00</span><span class="variable">.014</span>  INFO <span class="number">20217</span> --- [      Thread-<span class="number">19</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.springboot2</span><span class="variable">.xxl</span><span class="variable">.job</span><span class="variable">.job</span><span class="variable">.DemoJob</span>    : [execute][定时第 (<span class="number">4</span>) 次执行]</span><br></pre></td></tr></table></figure><p>并且，我们在调度中心的界面上，点击 <code>&quot;demoJob&quot;</code> 任务的「操作」按钮，选择「查询日志」，可以看到相应的调度日志。如下图：<img src="9.png" alt="查询日志"></p><p>至此，我们已经完成了 XXL-JOB 执行器的入门。</p><h1 id="6-快速入门-Elastic-Job"><a href="#6-快速入门-Elastic-Job" class="headerlink" title="6. 快速入门 Elastic-Job"></a>6. 快速入门 Elastic-Job</h1><blockquote><p>Elastic-Job 是一个分布式调度解决方案，由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。</p><p>Elastic-Job-Lite 定位为轻量级无中心化解决方案，使用 jar 包的形式提供分布式任务的协调服务。</p></blockquote><p>Elastic-Job 基本是国内开源<strong>最好</strong>的调度任务中间件的几个中间件，可能没有之一，嘿嘿。目前处于有点“断更”的状态，具体可见 <a href="https://github.com/elasticjob/elastic-job-lite/issues/616" target="_blank" rel="noopener">https://github.com/elasticjob/elastic-job-lite/issues/616</a> 。</p><p>所以关于这块的示例，艿艿暂时先不提供。如果对 Elastic-Job 源码感兴趣的胖友，可以看看「芋道源码」写的如下两个系列：</p><ul><li><a href="http://www.iocoder.cn/categories/Elastic-Job-Lite/?self" target="_blank" rel="noopener">《芋道 Elastic-Job-Lite 源码分析系列》</a></li><li><a href="http://www.iocoder.cn/categories/Elastic-Job-Cloud/?self" target="_blank" rel="noopener">《芋道 Elastic-Job-Cloud 源码分析系列》</a></li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p><strong>① 如何选择？</strong></p><p>可能胖友希望了解下不同调度中间件的对比。表格如下：</p><table><thead><tr><th align="left">特性</th><th align="left">quartz</th><th align="left">elastic-job-lite</th><th align="left">xxl-job</th><th align="left">LTS</th></tr></thead><tbody><tr><td align="left">依赖</td><td align="left">MySQL、jdk</td><td align="left">jdk、zookeeper</td><td align="left">mysql、jdk</td><td align="left">jdk、zookeeper、maven</td></tr><tr><td align="left">高可用</td><td align="left">多节点部署，通过竞争数据库锁来保证只有一个节点执行任务</td><td align="left">通过zookeeper的注册与发现，可以动态的添加服务器</td><td align="left">基于竞争数据库锁保证只有一个节点执行任务，支持水平扩容。可以手动增加定时任务，启动和暂停任务，有监控</td><td align="left">集群部署,可以动态的添加服务器。可以手动增加定时任务，启动和暂停任务。有监控</td></tr><tr><td align="left">任务分片</td><td align="left">×</td><td align="left">√</td><td align="left">√</td><td align="left">√</td></tr><tr><td align="left">管理界面</td><td align="left">×</td><td align="left">√</td><td align="left">√</td><td align="left">√</td></tr><tr><td align="left">难易程度</td><td align="left">简单</td><td align="left">简单</td><td align="left">简单</td><td align="left">略复杂</td></tr><tr><td align="left">高级功能</td><td align="left">-</td><td align="left">弹性扩容，多种作业模式，失效转移，运行状态收集，多线程处理数据，幂等性，容错处理，spring命名空间支持</td><td align="left">弹性扩容，分片广播，故障转移，Rolling实时日志，GLUE（支持在线编辑代码，免发布）,任务进度监控，任务依赖，数据加密，邮件报警，运行报表，国际化</td><td align="left">支持spring，spring boot，业务日志记录器，SPI扩展支持，故障转移，节点监控，多样化任务执行结果支持，FailStore容错，动态扩容。</td></tr><tr><td align="left">版本更新</td><td align="left">半年没更新</td><td align="left">2年没更新</td><td align="left">最近有更新</td><td align="left">1年没更新</td></tr></tbody></table><p>也推荐看看如下文章：</p><ul><li><a href="https://www.expectfly.com/2017/08/15/分布式定时任务方案技术选型/" target="_blank" rel="noopener">《分布式定时任务调度系统技术选型》</a></li><li><a href="https://my.oschina.net/centychen/blog/3044544" target="_blank" rel="noopener">《Azkaban、Xxl-Job 与 Airflow 对比分析》</a></li></ul><p>目前的状况，如果真的不知道怎么选择，可以先尝试下 <a href="https://www.xuxueli.com/xxl-job/" target="_blank" rel="noopener">XXL-JOB</a> 。</p><p><strong>② 中心化 V.S 去中心化</strong></p><p>下面，让我们一起来简单聊聊分布式调度中间件的实现方式的分类。一个分布式的调度中间件，会存在两种角色：</p><ul><li>调度器：负责调度任务，下发给执行器。</li><li>执行器：负责接收任务，执行具体任务。</li></ul><p>那么，如果从<strong>调度系统的角度</strong>来看，可以分成两类：</p><ul><li>中心化： 调度中心和执行器<strong>分离</strong>，调度中心统一调度，通知某个执行器处理任务。</li><li>去中心化：调度中心和执行器<strong>一体化</strong>，自己调度自己执行处理任务。</li></ul><p>如此可知 XXL-Job 属于<strong>中心化</strong>的任务调度平台。目前采用这种方案的还有：</p><ul><li>链家的 <a href="https://github.com/LianjiaTech/kob" target="_blank" rel="noopener">kob</a></li><li>美团的 Crane（暂未开源）</li></ul><p><strong>去中心化</strong>的任务调度平台，目前有：</p><ul><li><a href="http://elasticjob.io/docs/elastic-job-lite/00-overview/" target="_blank" rel="noopener">Elastic Job</a></li><li>唯品会的 <a href="https://github.com/vipshop/Saturn" target="_blank" rel="noopener">Saturn</a></li><li><a href="http://www.quartz-scheduler.org/" target="_blank" rel="noopener">Quartz</a> 基于数据库的集群方案</li><li>淘宝的 <a href="https://github.com/taobao/TBSchedule" target="_blank" rel="noopener">TBSchedule</a>（暂停更新，只能使用阿里云 <a href="https://cn.aliyun.com/aliware/schedulerx" target="_blank" rel="noopener">SchedulerX</a> 服务）</li></ul><blockquote><p>卡姆：如果胖友想要更加的理解，可以看看「芋道源码」朋友写的 <a href="https://www.jianshu.com/p/989298cf5314" target="_blank" rel="noopener">《中心化 V.S 去中心化调度设计》</a></p></blockquote><p><strong>③ 任务竞争 V.S 任务预分配</strong></p><p>那么，如果从<strong>任务分配的角度</strong>来看，可以分成两类：</p><ul><li>任务竞争：调度器会通过竞争任务，下发任务给执行器。</li><li>任务预分配：调度器预先分配任务给不同的执行器，无需进行竞争。</li></ul><p>如此可知 XXL-Job 属于<strong>任务竞争</strong>的任务调度平台。目前采用这种方案的还有：</p><ul><li>链家的 <a href="https://github.com/LianjiaTech/kob" target="_blank" rel="noopener">kob</a></li><li>美团的 Crane（暂未开源）</li><li><a href="http://www.quartz-scheduler.org/" target="_blank" rel="noopener">Quartz</a> 基于数据库的集群方案</li></ul><p><strong>任务预分配</strong>的任务调度平台，目前有：</p><ul><li><a href="http://elasticjob.io/docs/elastic-job-lite/00-overview/" target="_blank" rel="noopener">Elastic Job</a></li><li>唯品会的 <a href="https://github.com/vipshop/Saturn" target="_blank" rel="noopener">Saturn</a></li><li>淘宝的 <a href="https://github.com/taobao/TBSchedule" target="_blank" rel="noopener">TBSchedule</a>（暂停更新，只能使用阿里云 <a href="https://cn.aliyun.com/aliware/schedulerx" target="_blank" rel="noopener">SchedulerX</a> 服务）</li></ul><p>一般来说，基于任务预分配的任务调度平台，都会选择使用 Zookeeper 来协调分配任务到不同的节点上。同时，任务调度平台必须是去中心化的方案，每个节点即是调度器又是执行器。这样，任务在预分配在每个节点之后，后续就自己调度给自己执行。</p><p>相比较而言，随着节点越来越多，基于任务竞争的方案会因为任务竞争，导致存在性能下滑的问题。而基于任务预分配的方案，则不会存在这个问题。并且，基于任务预分配的方案，性能会优于基于任务竞争的方案。</p><p>这里在推荐一篇 Elastic Job 开发者张亮的文章 <a href="https://www.infoq.cn/article/dangdang-distributed-work-framework-elastic-job" target="_blank" rel="noopener">《详解当当网的分布式作业框架 elastic-job》</a> ，灰常给力！</p><p><strong>④ Quartz 是个优秀的调度内核</strong></p><p>绝大多数情况下，我们不会直接使用 Quartz 作为我们的调度中间件的选择。但是，基本所有的分布式调度中间件，都将 Quartz 作为调度内核，因为 Quartz 在单纯任务调度本身提供了很强的功能。</p><blockquote><p>XXL-JOB 最终选择自研调度组件（早期调度组件基于 Quartz）；</p><ul><li>一方面，是为了精简系统降低冗余依赖。</li><li>另一方面，是为了提供系统的可控度与稳定性。</li></ul></blockquote><p>在 Elastic-Job 3.X 的开发计划中，也有一项计划，就是自研调度组件，替换掉 Quartz 。</p></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/" title="Spring Boot 定时任务入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="prev"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" title="Spring Boot 异步任务入门"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a></li><li class="next"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" title="Spring Boot 数据库版本管理入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->